/**
 * 
 */
package cvcg.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;

import cvcg.dto.Top5DTO;
import cvcg.util.Top5Comparator;

/**
 * @author jaracje
 *
 */
@Entity
@Table(name="GAMESESSION")
@SuppressWarnings("serial")
public class GameSession implements Serializable
{
	public static final String STATUS_PHASE1 = "PHASE1";
	public static final String STATUS_PHASE2 = "PHASE2";
	public static final String STATUS_CLOSED = "CLOSED";
	
	@Id @GeneratedValue
	@Column(name="GAMESESSION_P")
	private Long id;	
	@Temporal(TemporalType.TIMESTAMP)
	private Date date;
	@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL,orphanRemoval=true)
	@JoinColumn(name="GAMESESSION_F")
	@ForeignKey(name = "CTR_ATTPLAYER_R_GAMESSESSION")
	@Fetch(FetchMode.SELECT)
	@Sort(type=SortType.COMPARATOR,comparator=AttendingPlayer.class)
	private SortedSet<AttendingPlayer> attendingPlayers = new TreeSet<AttendingPlayer>();
	@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL,orphanRemoval=true)
	@JoinColumn(name = "GAMESESSION_F")
	@ForeignKey(name = "CTR_VOTE_R_GAMESESSION")
	@Fetch(FetchMode.SELECT)
	private Set<Vote> votes = new HashSet<Vote>();
	@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL,orphanRemoval=true)
	@JoinColumn(name = "TOP_GAMESESSION_F")
	@ForeignKey(name = "CTR_VOTE_R_GAMESESSION_TOP")
	@Fetch(FetchMode.SELECT)
	private Set<Vote> topVotes = new HashSet<Vote>();
	@Column(nullable=false,length=6,columnDefinition="varchar(6) default '"+GameSession.STATUS_PHASE1+"'")
	private String status=STATUS_PHASE1;
	@ManyToMany(targetEntity=Game.class,fetch=FetchType.EAGER)
	@JoinTable(name = "PLAYEDGAME", joinColumns =
	{ @JoinColumn(name = "GAMESESSION_F") }, inverseJoinColumns =
	{ @JoinColumn(name = "GAME_F") })
	@ForeignKey(name = "CTR_PLGA_R_GAMESESSION", inverseName = "CTR_PLGA_R_GAME")
	@Fetch(FetchMode.SELECT)
	private Set<Game> playedGames = new HashSet<Game>();
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}
	
	public SortedSet<AttendingPlayer> getAttendingPlayers() {
		return attendingPlayers;
	}
	public void setAttendingPlayers(SortedSet<AttendingPlayer> attendingPlayers) {
		this.attendingPlayers = attendingPlayers;
	}
	public Set<Vote> getVotes() {
		return votes;
	}
	public void setVotes(Set<Vote> votes) {
		this.votes = votes;
	}
		
	public Set<Vote> getTopVotes() {
		return topVotes;
	}
	public void setTopVotes(Set<Vote> topVotes) {
		this.topVotes = topVotes;
	}
	
	
	public String getStatus() {
		return status;
	}
	public void setStatus(String status) {
		this.status = status;
	}
		
	/**
	 * @return the playedGames
	 */
	public Set<Game> getPlayedGames() {
		return playedGames;
	}
	/**
	 * @param playedGames the playedGames to set
	 */
	public void setPlayedGames(Set<Game> playedGames) {
		this.playedGames = playedGames;
	}
	/** 
	 * @return total attending player count
	 */
	@Transient
	public Integer getPlayerCount() 
	{
		int count = 0;
		if (attendingPlayers!=null) 
		{
			count = attendingPlayers.size();
		}
		return Integer.valueOf(count);
	}
	
	/**
	 * Obtain the total game count available for the current game session
	 * => sum of all attending players owned games (without duplicates)
	 * @return total number of owned games available for the game session 
	 */
	@Transient 
	public Integer getTotalGameCount() 
	{			
		Player player;
		Set<Game> ownedGames = new HashSet<Game>();
		
		if (attendingPlayers!=null) 
		{
			Integer attendingPlayerCount = Integer.valueOf(attendingPlayers.size());
			for (AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player!=null) 
				{
					ownedGames.addAll(player.getOwnedGamesForGameSession(attendingPlayerCount));
				}
			}			
		}
		return Integer.valueOf(ownedGames.size());		
	}
	
	/**
	 * Obtain the number of known games (rules mastered and owned by at least one player) 
	 * for the current game session.
	 * => sum of all owned games and known games from all attending player.
	 * Remark : a player can know a game he doesn't own...
	 * @return number of know games available for the game session
	 */
	@Transient
	public Integer getKnownGameCount() 
	{
		int count = 0;
		Player player;
		Set<Game> knownGames = new HashSet<Game>();
		
		if (attendingPlayers!=null) 
		{
			for (AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player.getRuleMasteredGames()!=null) 
				{
					knownGames.addAll(player.getRuleMasteredGames());
				}		
			}
			for(Game game : knownGames) 
			{
				for (AttendingPlayer attendingPlayer : attendingPlayers) 
				{
					player = attendingPlayer.getPlayer();
					if (player.isGameOwned(game))
					{
						count++;
						break;
					}
				}
			}
		}
		return Integer.valueOf(count);
	}
	
	/**
	 * Check if given player name is attending the session
	 * @param playerName player name
	 * @return true if player name is attending session, false otherwise
	 */
	@Transient
	public boolean isPlayerAttendingSession(String playerName) 
	{
		if (playerName!=null) 
		{
			Player player;
			for(AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player!=null) 
				{
					if (playerName.equals(player.getName()))
					{
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Add attending player 
	 * @param player to add
	 */
	@Transient
	public void addAttendingPlayer(Player player) 
	{
		AttendingPlayer attendingPlayer = new AttendingPlayer();
		attendingPlayer.setJoinDate(new Date());
		attendingPlayer.setPlayer(player);
		attendingPlayers.add(attendingPlayer);		
	}
	
	/**
	 * remove attending player (name based)
	 * @param playerName name of player to remove
	 */
	@Transient
	public void removeAttendingPlayer(String playerName) 
	{
		List<Vote> votesToRemove = new ArrayList<Vote>();
		if (playerName!=null) 
		{
			Player player = null;
			for(AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player!=null) 
				{
					if (playerName.equals(player.getName()))
					{
						attendingPlayers.remove(attendingPlayer);
						for(Vote vote : votes) 
						{
							if (vote.getPlayer()!=null && player.getId().compareTo(vote.getPlayer().getId())==0) 
							{
								votesToRemove.add(vote);
							}							
						}
						votes.removeAll(votesToRemove);
						break;
					}					
				}
			}
		}
	}
	
	/**
	 * List of available games for current game session
	 * => game is owned by at least one attending player and is matching session's player count
	 * @return list of game
	 */
	@Transient 
	public List<Game> getAvailableGames() 
	{
		List<Game> availableGames = new ArrayList<Game>();
		Set<Game> ownedGames = new HashSet<Game>();
		Player player;
		if (attendingPlayers!=null) 
		{			
			for (AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player!=null) 
				{
					ownedGames.addAll(player.getOwnedGamesForGameSession(attendingPlayers.size()));
				}
			}			
		}
		availableGames.addAll(ownedGames);
		return availableGames;
	}
	
	/**
	 * List of available known games for current game session
	 * => game is known and owned by at least one attending player and is matching session's player count
	 * @return list of game
	 */
	@Transient 
	public List<Game> getAvailableKnownGames() 
	{
		List<Game> availableGames = new ArrayList<Game>();		
		Player player;
		Set<Game> knownGames = new HashSet<Game>();
		
		if (attendingPlayers!=null) 
		{
			for (AttendingPlayer attendingPlayer : attendingPlayers) 
			{
				player = attendingPlayer.getPlayer();
				if (player.getRuleMasteredGames()!=null) 
				{
					knownGames.addAll(player.getRuleMasteredGamesForGameSession(attendingPlayers.size()));
				}		
			}
			for(Game game : knownGames) 
			{
				for (AttendingPlayer attendingPlayer : attendingPlayers) 
				{
					player = attendingPlayer.getPlayer();
					if (player.isGameOwned(game))
					{
						availableGames.add(game);
						break;
					}
				}
			}
		}
		return availableGames;
	}

	@Transient
	public void addGameSessionVote(Vote vote) 
	{
		votes.add(vote);
	}
	
	@Transient
	public void removeGameSessionVote(Vote vote) 
	{
		votes.remove(vote);
	}

	
	@Transient
	public Set<Long> getVotedGameIdByPlayerName(String playerName) 
	{
		Player player;
		Game game;
		Set<Long> votedGameIds = new HashSet<Long>();
		if (playerName!=null) 
		{
			for(Vote vote : votes) 
			{
				player = vote.getPlayer();
				game = vote.getGame();
				if (player!=null && game!=null) 
				{
					if(playerName.equals(player.getName())) 
					{
						votedGameIds.add(game.getId());
					}
				}
			}
		}
		return votedGameIds;
	}
	
	@Transient
	public Vote getGameSessionVoteByGameIdAndPlayerName(Long gameId,String playerName) 
	{
		Player player;
		Game game;
		if (playerName!=null && gameId !=null) 
		{
			for(Vote vote : votes) 
			{
				player = vote.getPlayer();
				game = vote.getGame();
				if (player!=null && game!=null && gameId.compareTo(game.getId())==0 && playerName.equals(player.getName())) 
				{
					return vote;					
				}
			}
		}
		return null;
	}
	
	@Transient
	public final void goNextPhase() 
	{
		if(STATUS_PHASE1.equals(status)) 
		{
			setStatus(STATUS_PHASE2);
		} else if (STATUS_PHASE2.equals(status)) {
			setStatus(STATUS_CLOSED);
		}
	}
	
	@Transient
	public boolean isPhase1() 
	{
		return STATUS_PHASE1.equals(status);
	}
	
	@Transient
	public boolean isPhase2() 
	{
		return STATUS_PHASE2.equals(status);
	}
	
	@Transient
	public boolean isClosed() 
	{
		return STATUS_CLOSED.equals(status);
	}
	
	@Transient
	public Set<Vote> getVotesByPlayerName(String playerName)
	{
		Set<Vote> votesByPlayer= new HashSet<Vote>();
		if (playerName!=null) 
		{
			for (Vote vote : votes) 
			{
				if (playerName.equals(vote.getPlayerName())) 
				{
					votesByPlayer.add(vote);
				}
			}
		}
		return votesByPlayer;
	}

	@Transient
	public List<Vote> getTopVotesByPlayerName(String playerName)
	{
		List<Vote> topVotesByPlayer= new ArrayList<Vote>();
		if (playerName!=null) 
		{
			for (Vote vote : topVotes) 
			{
				if (playerName.equals(vote.getPlayerName())) 
				{
					topVotesByPlayer.add(vote);
				}
			}
		}
		return topVotesByPlayer;
	}

	@Transient 
	public Vote getTopVoteByPlayerIdAndGameId(Long playerId,Long gameId) 
	{		
		if (playerId!=null && gameId !=null) 
		{ 
			for (Vote vote : topVotes) 
			{
				if (playerId.equals(vote.getPlayerId()) && gameId.equals(vote.getGameId())) 
				{
					return vote;
				}
			}
		}
		return null;
	}
	
	@Transient
	public void addTopVote(Vote topVote) 
	{
		topVotes.add(topVote);
	}
	
	@Transient
	public void removeTop5ByPlayer(Long playerId) 
	{
		Set<Vote> votesToRemove = new HashSet<Vote>();
		for (Vote vote : topVotes) 
		{
			if (playerId.equals(vote.getPlayerId())) 
			{
				votesToRemove.add(vote);
			}
		}
		topVotes.removeAll(votesToRemove);
	}
		
	
	@Transient
	public List<Top5DTO> getTop5Winner() 
	{
		 List<Top5DTO> top5List = new ArrayList<Top5DTO>();
		 Long gameId;
		 Top5DTO top5;
		 Integer points;
		 for (Vote vote : topVotes) 
		 {
			 gameId = vote.getGameId();
			 top5 = findTop5DTOElement(top5List, gameId);
			 if (top5 == null) 
			 {
				 top5 = new Top5DTO();
				 top5.setGameId(gameId);
				 top5.setGameName(vote.getGameName());
				 top5.setPoints(Integer.valueOf(0));
				 top5List.add(top5);
			 }
			 points = top5.getPoints() + vote.getWeight();
			 top5.setPoints(points);
		 }
		 Collections.sort(top5List, new Top5Comparator());
		 return top5List.subList(0,top5List.size()>=5?5:(top5List.size()));
	}
	
	@Transient
	private Top5DTO findTop5DTOElement(List<Top5DTO> list,Long gameId) 
	{
		if (gameId!=null)
		for (Top5DTO top5 : list) 
		{
			if (gameId.compareTo(top5.getGameId())==0) 
			{
				return top5;
			}
		}
		
		return null;
	}
	
	@Transient
	public void addPlayedGame(Game game) 
	{
		playedGames.add(game);
	}
	
	@Transient
	public void removeAllPlayedGames() 
	{
		playedGames.clear();
	}
}
